package com.oj.core.impl;


import com.oj.api.core.CoreApi;
import com.oj.api.core.constants.JudgeConstant;
import com.oj.api.core.domain.*;
import com.oj.api.core.service.IJudgeService;
import com.oj.api.core.service.IProblemSetCategoryService;
import com.oj.common.InfoPage;
import com.oj.common.JsonResponse;
import com.oj.common.Response;
import com.oj.common.SystemTool;
import com.oj.common.constants.OperTypeEnum;
import com.oj.common.exception.BreezeeException;
import com.oj.common.util.BreezeeUtils;
import com.oj.common.util.ContextUtil;
import com.oj.core.entity.*;
import com.oj.core.repository.JudgeRepository;
import com.oj.core.util.OJCoreJava;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.stereotype.Service;

import java.io.*;
import java.nio.file.Files;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

/**
 * 评测服务
 * Created by Bing on 2018/5/4.
 */
@Service(CoreApi.BEAN_JUDGE_SERVICE)
public class JudgeServiceImpl implements IJudgeService {

    @Autowired
    private JudgeRepository judgeRepository;

    @Override
    public JudgeInfo saveInfo(JudgeInfo judgeInfo) throws BreezeeException {
        return null;
    }

    @Override
    public JudgeInfo findInfoById(String id) throws BreezeeException {
        JudgeEntity entity = new JudgeEntity().buildOne(id).findEntity();
        if (entity != null) {
            return entity.buildInfo(new JudgeInfo());
        }
        return null;
    }

    @Override
    public List<JudgeInfo> listInfos(JudgeInfo judgeInfo) {
        return new JudgeEntity().createWithInfo(judgeInfo).list();
    }

    @Override
    public InfoPage<JudgeInfo> pageInfos(JudgeInfo judgeInfo) {
        return new JudgeEntity().createWithInfo(judgeInfo).page();
    }

    @Override
    public void deleteInfo(String id) throws BreezeeException {

    }

    @Override
    public JudgeInfo judge(JudgeInfo judgeInfo) {

        /**
         * 先保存至数据库
         */
        judgeInfo.setStatus(JudgeConstant.JUDGING.getValue());
        judgeInfo.setOperType(OperTypeEnum.WRITE);
        judgeInfo.setCode(SystemTool.getCode());
        judgeInfo.setScore(0);
        judgeInfo.setRunTime(0L);
        judgeInfo.setRunMemory(0L);
        JudgeEntity judgeEntity = new JudgeEntity().createWithInfo(judgeInfo);
        System.out.println("第一次save()");
        JudgeInfo info = judgeEntity.save();
        //评测
        JudgeInfo finalJudgeInfo = judgeInfo;
        new Thread() {
            @Override
            public void run() {

                /**
                 * 1.保存源代码
                 * 2.编译
                 * 3.运行，结果校验
                 *
                 * 删除临时文件
                 */
                String code = finalJudgeInfo.getOriginCode();
                String compileCmd = null, runCmd = null;
                File originFile = null, targetFile = null,javaExe = null;
                String exeName = null;
                if ("Java(openjdk)".equals(finalJudgeInfo.getCompile())) {
                    //去掉package
                    if (code.indexOf("package") >= 0) {
                        code = code.substring(code.indexOf(";") + 1);
                    }
                    //获取类名
                    String temp = code.substring(code.indexOf(" class ") + 7).trim();
                    String className = temp.substring(0, temp.indexOf(" "));
                    //替换类名
                    String fileName = BreezeeUtils.getRandomChar(10);
                    code = code.replaceAll(className, fileName);
                    System.out.println(code);

                    FileOutputStream outputStream = null;
                    String filePath = fileName + ".java";

                    originFile = new File(filePath);
                    targetFile = new File(fileName + ".class");
                    javaExe = new File(System.getProperty("java.home")+"/bin/"+fileName+".exe");
                    if(javaExe.exists()){
                        javaExe.delete();
                    }
                    try {
                        Files.copy(new File(System.getProperty("java.home")+"/bin/java.exe").toPath(),javaExe.toPath());
                        compileCmd = "cmd /c javac " + filePath;
                        runCmd = "cmd /c "+fileName+" " + fileName;
                        exeName = fileName+".exe";
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                } else if ("C(gcc)".equals(finalJudgeInfo.getCompile())) {
                    String fileName = BreezeeUtils.getRandomChar(10);
                    String filePath = fileName + ".c";
                    originFile = new File(filePath);
                    targetFile = new File(fileName + ".exe");
                    compileCmd = "C:\\TDM-GCC-64\\bin\\gcc -o " + fileName + ".exe " + fileName + ".c";
                    runCmd = fileName + ".exe";
                    exeName = fileName+".exe";
                }

                //1.保存源文件
                FileOutputStream fos = null;
                try {
                    fos = new FileOutputStream(originFile);
                    fos.write(code.getBytes());
                    //2.编译文件
                    System.out.println(System.getProperties().get("user.dir"));
                    JsonResponse compileResponse = compile(compileCmd);

                    if (compileResponse.isSuccess()) {
                        finalJudgeInfo.setStatus(JudgeConstant.COMPILE_SUCCESS.getValue());
                        //3.运行 校验
                        List<JudgeLineInfo> lines = exec(runCmd, finalJudgeInfo.getProblemCode(),exeName);
                        judgeEntity.setStatus(JudgeConstant.ANSWER_CORRECT.getValue());
                        for (JudgeLineInfo line : lines) {
                            line.setOperType(OperTypeEnum.WRITE);
                            line.setCode(finalJudgeInfo.getCode() + "-" + line.getCode());
                            if (!line.getStatus().equals(JudgeConstant.ANSWER_CORRECT.getValue())) {
                                judgeEntity.setStatus(line.getStatus());
                                judgeEntity.setRemark(line.getRemark());
                                if(line.getStatus().equals(JudgeConstant.EXCEPTION.getValue())){
                                    judgeEntity.setRemark(line.getProperties().get("errMsg").toString());
                                }
                            }
                            if (line.getRunTime() > judgeEntity.getRunTime()) {
                                judgeEntity.setRunTime(line.getRunTime());
                            }
                            if (line.getRunMemory() > judgeEntity.getRunMemory()) {
                                judgeEntity.setRunMemory(line.getRunMemory());
                            }
                            if(line.getRemark()!=null&&line.getRemark().length()>300){
                                line.setRemark(line.getRemark().substring(0,300));
                            }
                            judgeEntity.setScore(judgeEntity.getScore() + line.getScore());
                            judgeEntity.addLine(new JudgeLineEntity().createWithInfo(line));
                        }
                        if(judgeEntity.getRemark()!=null&&judgeEntity.getRemark().length()>300){
                            judgeEntity.setRemark(judgeEntity.getRemark().substring(0,300));
                        }
                        judgeEntity.save();
                    } else {
                        judgeEntity.setLines(new HashSet<>());
                        judgeEntity.setStatus(JudgeConstant.COMPILE_ERROR.getValue());
                        judgeEntity.setRemark(compileResponse.getMsg());
                        if(judgeEntity.getRemark()!=null&&judgeEntity.getRemark().length()>300){
                            judgeEntity.setRemark(judgeEntity.getRemark().substring(0,300));
                        }
                        judgeEntity.save();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        fos.close();
                        //4. 删除
                        System.gc();
                        if(javaExe!=null&&javaExe.exists()){
                            javaExe.delete();
                        }
                        originFile.delete();
                        targetFile.delete();
                        System.gc();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

            }
        }.start();
        JudgeEntity entity = new JudgeEntity().buildCode(info.getCode()).findEntity();
        return entity.buildInfo();
    }

    private JsonResponse compile(String command) throws IOException {
        Runtime runtime = Runtime.getRuntime();
        Process process = runtime.exec(command);
        //取得命令结果的输出流

        //用缓冲器读行
        BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream(),"GBK"));
        BufferedReader errBr = new BufferedReader(new InputStreamReader(process.getErrorStream(),"GBK"));
        String line = null, errLine = null;
        StringBuilder msg = new StringBuilder(), errMsg = new StringBuilder();

        //直到读完为止
        while ((line = br.readLine()) != null) {
            msg.append(line);
        }
        while ((errLine = errBr.readLine()) != null) {
            errMsg.append(errLine + "\n");
        }
        br.close();
        errBr.close();

        if (errMsg.length() > 0) {
            return JsonResponse.error(errMsg.toString());
        } else {
            return JsonResponse.ok().buildMsg(msg.toString());
        }
    }

    private List<JudgeLineInfo> exec(String command, String problemCode,String exeName) throws IOException {

        ProblemEntity problem = new ProblemEntity().buildCode(problemCode).findEntity();

        if (problem == null) {
            return null;
        }
        List<JudgeLineInfo> lines = new ArrayList<>();

        String parentPath = "C:\\ojData\\";

        File testDataDir = new File(parentPath + problem.getTestDataCode());

        Map<String, File> fileNames = new HashMap<>();
        List<Map<String, String>> scores = new ArrayList<>();
        File[] files = testDataDir.listFiles();
        for (File f : files) {
//            System.out.println("fileName:"+f.getName());
            fileNames.put(f.getName(), f);
        }
        BufferedReader scoreBr = new BufferedReader(new InputStreamReader(new FileInputStream(fileNames.get("scores.txt")),"utf-8"));
        String scoreLine = null;
        Map<String, String> m = null;
        while ((scoreLine = scoreBr.readLine()) != null) {
            String[] data =scoreLine.split(" ");
            m = new HashMap<>();
//            System.out.println("打印出来是什么？"+data[0].getBytes()+"那一个0呢？"+"0".getBytes());
            m.put("number", "0");
            m.put("score", data[1]);
            m.put("remark", data[2]);
            scores.add(m);
        }

        StringBuilder errMsg = new StringBuilder();

        for (Map<String, String> map : scores) {
            Runtime runtime = Runtime.getRuntime();
            System.gc();
            long startFreeMemory = runtime.freeMemory();//Java 虚拟机中的空闲内存量
            Process process = runtime.exec(command);

            JudgeLineInfo info = new JudgeLineInfo();
            info.setRemark(map.get("remark"));
            info.setCode(map.get("number"));
            info.setName(map.get("number"));
            info.setScore(0);
            lines.add(info);

            long startCurrentTime = System.currentTimeMillis();//获取系统当前时间

            new Timer().schedule(new TimerTask() {
                @Override
                public void run() {
                    Long useTime = System.currentTimeMillis() - startCurrentTime;
                    if (process.isAlive()) {
                        if (useTime > problem.getLimitTime()) {
                            try {
                                System.out.println("destory "+exeName);
                                process.destroy();
                                Process p = Runtime.getRuntime().exec(new String[]{"TaskKill", "/F", "/IM", exeName});
                                BufferedReader errMsg = new BufferedReader(new InputStreamReader(p.getErrorStream(),"GBK"));
                                String msgLine = null;
                                System.out.println("xxxx");
                                while ((msgLine = errMsg.readLine()) != null) {
                                    System.out.println(msgLine);
                                }

                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }else {
                        try {
                            System.out.println("关闭 Windows 问题报告");
                            Process p = Runtime.getRuntime().exec("cmd /C C:\\ojData\\xx.bat");
                            BufferedReader errMsg = new BufferedReader(new InputStreamReader(p.getErrorStream(),"GBK"));
                            String msgLine = null;
                            while ((msgLine = errMsg.readLine()) != null) {
                                System.out.println(msgLine);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        } finally {
                            this.cancel();
                        }
                    }
                }
            }, 2000, 100);

            OutputStream os = null;
            os = process.getOutputStream();
            BufferedReader proIn = new BufferedReader(new InputStreamReader(new FileInputStream(fileNames.get((map.get("number") + ".in").replaceAll("\uFEFF", "")))));
            String in = null;
            while ((in = proIn.readLine()) != null) {
                System.out.println("输入：" + in);
                if (process.isAlive()){
                    os.write((in + "\n").getBytes());//  \n  很重要
                    os.flush();
                }
            }
            System.out.println("input end .....");
            //程序输出
            BufferedReader proOut = new BufferedReader(new InputStreamReader(process.getInputStream(),"GBK"));
            //运行报错
            BufferedReader errBr = new BufferedReader(new InputStreamReader(process.getErrorStream(),"GBK"));
            //获取真正的结果
            BufferedReader result = new BufferedReader(new InputStreamReader(new FileInputStream(fileNames.get((map.get("number") + ".out").replaceAll("\uFEFF", "")))));
            String outc = null;
            String resultc = null;
            boolean resultFlag = false;
            while ((outc = proOut.readLine()) != null) {
                System.out.println("结果：" + outc);
                resultc = result.readLine();
                if (resultc.equals(outc)) {//自己加断点看结果  输出已经重置了 没办法输出到控制台
                    resultFlag = true;
                } else {
                    resultFlag = false;
                    break;
                }
            }
            String errLine = null;
            while ((errLine = errBr.readLine()) != null) {
                errMsg.append(errLine + "\n");
            }
            long useTime = System.currentTimeMillis() - startCurrentTime;
            os.close();
            proIn.close();
            proOut.close();
            result.close();
            errBr.close();
            //System.runFinalization();
            //TODO: 此处应该 用Timer控制，如果时间或内存超出 杀死
            long useMemory = (startFreeMemory - runtime.freeMemory());//Bit  /1024.0

            info.setRunTime(useTime);
            info.setRunMemory(useMemory);
            if (useTime > problem.getLimitTime()) {
                info.setStatus(JudgeConstant.TIME_OUT.getValue());
                continue;
            }
            if (useMemory > (problem.getLimitMoney() * 1024 * 1024)) {
                info.setStatus(JudgeConstant.MEMORY_OUT.getValue());
                continue;
            }
            if (resultFlag) {
                info.setScore(Integer.parseInt(map.get("score")));
                info.setStatus(JudgeConstant.ANSWER_CORRECT.getValue());
            } else {
                if (errMsg.length() > 0) {
                    info.setStatus(JudgeConstant.EXCEPTION.getValue());
                    info.addProperties("errMsg",errMsg.toString());
                } else {
                    info.setStatus(JudgeConstant.ANSWER_ERROR.getValue());
                }
            }

            System.out.println("Test  Data End ......");

        }
        scoreBr.close();
        System.out.println("judge end...............");
        return lines;
    }


    @Override
    public InfoPage findRankOfProblemSet(JudgeInfo judgeInfo) {


        String problemSetCode = (String) judgeInfo.getProperties().get("problemSetCode");
        String userCode = (String) judgeInfo.getProperties().get("userCode");
        Integer pageNumber = (Integer) judgeInfo.getProperties().get("pageNumber");//页数
        Integer pageSize = (Integer) judgeInfo.getProperties().get("pageSize");//每页多少

        JdbcTemplate jdbcTemplate = ContextUtil.getBean("jdbcTemplate", JdbcTemplate.class);
        List<Map<String, Object>> data = jdbcTemplate.queryForList("SELECT *\n" +
                "                from (SELECT @ROWNO := @ROWNO + 1 AS rowNo, b.*\n" +
                "        from  (select a.creator,SUM(a.score) as score,count(a.status=50 or null) as acceptNum\n" +
                "        from\n" +
                "                (select *\n" +
                "                                from core_judge\n" +
                "                        where problem_code\n" +
                "                        in (select code from core_problem where pk_id in (select pe_id from core_pse_pe where pse_id = (select pk_id from core_problem_set where code = '" + problemSetCode + "')))  and problem_set_code = '"+problemSetCode+"' ) as a\n" +
                "        GROUP BY a.creator\n" +
                "        ORDER BY SUM(a.score) desc) as b,\n" +
                "        (SELECT @ROWNO := 0) T\n" +
                "        ORDER BY ROWNO) c\n" +
                "        LIMIT " + pageNumber * pageSize + "," + pageSize + "");

        final Long[] total = {null};
        jdbcTemplate.query("SELECT count(*) as total \n" +
                "        from (SELECT @ROWNO := @ROWNO + 1 AS rowNo, b.*\n" +
                "        from  (select a.creator,SUM(a.score) as score,count(a.status=50 or null) as acceptNum\n" +
                "        from\n" +
                "                (select *\n" +
                "                                from core_judge\n" +
                "                        where problem_code\n" +
                "                        in (select code from core_problem where pk_id in (select pe_id from core_pse_pe where pse_id = (select pk_id from core_problem_set where code = '" + problemSetCode + "')))  and problem_set_code = '"+problemSetCode+"' ) as a\n" +
                "        GROUP BY a.creator\n" +
                "        ORDER BY SUM(a.score) desc) as b,\n" +
                "        (SELECT @ROWNO := 0) T\n" +
                "        ORDER BY ROWNO) c", new RowCallbackHandler() {
            @Override
            public void processRow(ResultSet rs) throws SQLException {
                total[0] = rs.getLong("total");
            }
        });

        List<Map<String, Object>> myData = jdbcTemplate.queryForList("SELECT * \n" +
                "        from (SELECT @ROWNO := @ROWNO + 1 AS rowNo, b.*\n" +
                "        from  (select a.creator,SUM(a.score) as score,count(a.status=50 or null) as acceptNum\n" +
                "        from\n" +
                "                (select *\n" +
                "                                from core_judge\n" +
                "                        where problem_code\n" +
                "                        in (select code from core_problem where pk_id in (select pe_id from core_pse_pe where pse_id = (select pk_id from core_problem_set where code = '" + problemSetCode + "')))  and problem_set_code = '"+problemSetCode+"') as a\n" +
                "        GROUP BY a.creator\n" +
                "        ORDER BY SUM(a.score) desc) as b,\n" +
                "        (SELECT @ROWNO := 0) T\n" +
                "        ORDER BY ROWNO) c" +
                "       where c.creator = '" + userCode + "'");

        List<Map<String, Object>> rows = new ArrayList<>();
       /* rows.addAll(myData);*/
        rows.addAll(data);
        return new InfoPage(rows, total[0]);
    }
}
